"""
Basic data structure used for general trading function in the trading platform.
"""

from dataclasses import dataclass, field
from datetime import datetime
from logging import INFO
from typing import Optional

from .constant import Direction, Exchange, Interval, Offset, Status, Product, OptionType, OrderType

ACTIVE_STATUSES = set([Status.SUBMITTING, Status.NOTTRADED, Status.PARTTRADED])



@dataclass
class FixedAssetData(BaseData):
    """固收类标的资产都应该继承这个类"""
    symbol: str
    asset_type: AssetType
    
    @abstractmethod
    def get_cashflows(self, start_date: datetime, end_date: datetime) -> List[CashFlowEvent]:
        """获取指定时间段内的现金流事件"""
        pass
    
    @abstractmethod
    def is_matured(self, current_date: datetime) -> bool:
        """判断资产是否到期"""
        pass
    
    @abstractmethod
    def accrued_interest(self, current_date: datetime, quantity: float) -> float:
        """计算应计利息"""
        return 0

@dataclass
class BaseData:
    """
    Any data object needs a gateway_name as source
    and should inherit base data.
    """

    gateway_name: str

    extra: Optional[dict] = field(default=None, init=False)
    



@dataclass
class TickData(BaseData):
    """
    Tick data contains information about:
        * last trade in market
        * orderbook snapshot
        * intraday market statistics.
    """

    symbol: str
    exchange: Exchange
    datetime: datetime

    name: str = ""
    volume: float = 0
    turnover: float = 0
    open_interest: float = 0
    last_price: float = 0
    last_volume: float = 0
    limit_up: float = 0
    limit_down: float = 0

    open_price: float = 0
    high_price: float = 0
    low_price: float = 0
    pre_close: float = 0

    bid_price_1: float = 0
    bid_price_2: float = 0
    bid_price_3: float = 0
    bid_price_4: float = 0
    bid_price_5: float = 0

    ask_price_1: float = 0
    ask_price_2: float = 0
    ask_price_3: float = 0
    ask_price_4: float = 0
    ask_price_5: float = 0

    bid_volume_1: float = 0
    bid_volume_2: float = 0
    bid_volume_3: float = 0
    bid_volume_4: float = 0
    bid_volume_5: float = 0

    ask_volume_1: float = 0
    ask_volume_2: float = 0
    ask_volume_3: float = 0
    ask_volume_4: float = 0
    ask_volume_5: float = 0

    localtime: datetime = None

    def __post_init__(self) -> None:
        """"""
        self.vt_symbol: str = f"{self.symbol}.{self.exchange.value}"


@dataclass
class BarData(BaseData):
    """
    Candlestick bar data of a certain trading period.
    """

    symbol: str
    exchange: Exchange
    datetime: datetime

    interval: Interval = None
    volume: float = 0
    turnover: float = 0
    open_interest: float = 0
    open_price: float = 0
    high_price: float = 0
    low_price: float = 0
    close_price: float = 0

    def __post_init__(self) -> None:
        """"""
        self.vt_symbol: str = f"{self.symbol}.{self.exchange.value}"


@dataclass
class OrderData(BaseData):
    """
    Order data contains information for tracking lastest status
    of a specific order.
    """

    symbol: str
    exchange: Exchange
    orderid: str

    type: OrderType = OrderType.LIMIT
    direction: Direction = None
    offset: Offset = Offset.NONE
    price: float = 0
    volume: float = 0
    traded: float = 0
    status: Status = Status.SUBMITTING
    datetime: datetime = None
    reference: str = ""

    def __post_init__(self) -> None:
        """"""
        self.vt_symbol: str = f"{self.symbol}.{self.exchange.value}"
        self.vt_orderid: str = f"{self.gateway_name}.{self.orderid}"

    def is_active(self) -> bool:
        """
        Check if the order is active.
        """
        return self.status in ACTIVE_STATUSES

    def create_cancel_request(self) -> "CancelRequest":
        """
        Create cancel request object from order.
        """
        req: CancelRequest = CancelRequest(
            orderid=self.orderid, symbol=self.symbol, exchange=self.exchange
        )
        return req


@dataclass
class TradeData(BaseData):
    """
    Trade data contains information of a fill of an order. One order
    can have several trade fills.
    """

    symbol: str
    exchange: Exchange
    orderid: str
    tradeid: str
    direction: Direction = None

    offset: Offset = Offset.NONE
    price: float = 0
    volume: float = 0
    datetime: datetime = None

    def __post_init__(self) -> None:
        """"""
        self.vt_symbol: str = f"{self.symbol}.{self.exchange.value}"
        self.vt_orderid: str = f"{self.gateway_name}.{self.orderid}"
        self.vt_tradeid: str = f"{self.gateway_name}.{self.tradeid}"


@dataclass
class PositionData(BaseData):
    """
    Position data is used for tracking each individual position holding.
    """

    symbol: str
    exchange: Exchange
    direction: Direction

    volume: float = 0
    frozen: float = 0
    price: float = 0
    pnl: float = 0
    yd_volume: float = 0

    def __post_init__(self) -> None:
        """"""
        self.vt_symbol: str = f"{self.symbol}.{self.exchange.value}"
        self.vt_positionid: str = f"{self.gateway_name}.{self.vt_symbol}.{self.direction.value}"


@dataclass
class AccountData(BaseData):
    """
    Account data contains information about balance, frozen and
    available.
    """

    accountid: str

    balance: float = 0
    frozen: float = 0

    def __post_init__(self) -> None:
        """"""
        self.available: float = self.balance - self.frozen
        self.vt_accountid: str = f"{self.gateway_name}.{self.accountid}"


@dataclass
class LogData(BaseData):
    """
    Log data is used for recording log messages on GUI or in log files.
    """

    msg: str
    level: int = INFO

    def __post_init__(self) -> None:
        """"""
        self.time: datetime = datetime.now()


@dataclass
class ContractData(BaseData):
    """
    Contract data contains basic information about each contract traded.
    """

    symbol: str
    exchange: Exchange
    name: str
    product: Product
    size: float
    pricetick: float

    min_volume: float = 1           # minimum order volume
    max_volume: float = None        # maximum order volume
    stop_supported: bool = False    # whether server supports stop order
    net_position: bool = False      # whether gateway uses net position volume
    history_data: bool = False      # whether gateway provides bar history data

    option_strike: float = 0
    option_underlying: str = ""     # vt_symbol of underlying contract
    option_type: OptionType = None
    option_listed: datetime = None
    option_expiry: datetime = None
    option_portfolio: str = ""
    option_index: str = ""          # for identifying options with same strike price

    def __post_init__(self) -> None:
        """"""
        self.vt_symbol: str = f"{self.symbol}.{self.exchange.value}"


@dataclass
class QuoteData(BaseData):
    """
    Quote data contains information for tracking lastest status
    of a specific quote.
    """

    symbol: str
    exchange: Exchange
    quoteid: str

    bid_price: float = 0.0
    bid_volume: int = 0
    ask_price: float = 0.0
    ask_volume: int = 0
    bid_offset: Offset = Offset.NONE
    ask_offset: Offset = Offset.NONE
    status: Status = Status.SUBMITTING
    datetime: datetime = None
    reference: str = ""

    def __post_init__(self) -> None:
        """"""
        self.vt_symbol: str = f"{self.symbol}.{self.exchange.value}"
        self.vt_quoteid: str = f"{self.gateway_name}.{self.quoteid}"

    def is_active(self) -> bool:
        """
        Check if the quote is active.
        """
        return self.status in ACTIVE_STATUSES

    def create_cancel_request(self) -> "CancelRequest":
        """
        Create cancel request object from quote.
        """
        req: CancelRequest = CancelRequest(
            orderid=self.quoteid, symbol=self.symbol, exchange=self.exchange
        )
        return req
@dataclass
class BondData(FixedAssetData):
    """债券类"""
    par_value: float = 100.0  # 面值
    coupon_rate: Optional[float] = None  # 票面利率
    issue_date: datetime = field(default_factory=datetime.now)
    maturity_date: datetime = field(default_factory=lambda: datetime.now() + timedelta(days=365))
    payment_frequency: int = 1  # 每年付息次数
    explicit_cashflows: Optional[List[Tuple[datetime, float, CashFlowType]]] = None  # 明确的现金流列表，每项为(日期, 金额, 类型)
    
    def __post_init__(self):
        """初始化后处理"""
        self.asset_type = AssetType.BOND
        # self.payment_dates = self._generate_payment_schedule()
        
        # 如果提供了明确的现金流，按日期排序
        if self.explicit_cashflows:
            self.explicit_cashflows.sort(key=lambda x: x[0])
    
    @property
    def payment_dates(self) -> List[datetime]:
        """获取所有付息日期，包括到期日"""
        if self.explicit_cashflows:
            return [date for date, _, flow_type in self.explicit_cashflows 
                if flow_type == CashFlowType.INTEREST]
        
        if self.coupon_rate == 0:  # 零息债券
            return []
            
        # 计算付息日期
        dates = []
        days_between_payments = 365 // self.payment_frequency
        
        # 计算第一个付息日
        first_payment = self.issue_date + timedelta(days=days_between_payments)
        
        # 如果第一个付息日超过到期日，则返回到期日
        if first_payment > self.maturity_date:
            return [self.maturity_date]
            
        # 计算所有付息日
        current_date = first_payment
        while current_date < self.maturity_date:  
            dates.append(current_date)
            current_date += timedelta(days=days_between_payments)
            
        # 最后一次付息一定是到期日
        if not dates or dates[-1] != self.maturity_date:
            dates.append(self.maturity_date)
            
        return sorted(dates)

    def _get_payment_amount(self, payment_date: datetime) -> float:
        """获取付息金额"""
        if self.explicit_cashflows:
            for date, amount, flow_type in self.explicit_cashflows:
                if date == payment_date and flow_type == CashFlowType.INTEREST:
                    return amount
        return self.par_value * self.coupon_rate / self.payment_frequency

    def get_cashflows(self, start_date: datetime, end_date: datetime) -> List[CashFlowEvent]:
        """获取指定时间段内的现金流事件"""
        # 确保日期是datetime类型
        if isinstance(start_date, int):
            start_date = datetime.fromtimestamp(start_date)
        if isinstance(end_date, int):
            end_date = datetime.fromtimestamp(end_date)
        
        cashflows = []
        
        # 如果债券在此期间到期，添加到期事件（无论是否有明确现金流）
        if start_date <= self.maturity_date <= end_date:
            # 到期事件
            cashflows.append(
                CashFlowEvent(
                    type=EventType.CASHFLOW,
                    timestamp=self.maturity_date,
                    asset_type=AssetType.BOND,
                    symbol=self.symbol,
                    amount=0,  # 到期事件不涉及实际现金流
                    cashflow_type=CashFlowType.MATURITY,
                    related_position=self.symbol
                )
            )
        
        # 如果有明确的现金流，直接使用
        if self.explicit_cashflows:
            for date, amount, flow_type in self.explicit_cashflows:
                if start_date <= date <= end_date:
                    cashflows.append(
                        CashFlowEvent(
                            type=EventType.CASHFLOW,
                            timestamp=date,
                            asset_type=AssetType.BOND,
                            symbol=self.symbol,
                            amount=amount,
                            cashflow_type=flow_type,
                            related_position=self.symbol
                        )
                    )
            return sorted(cashflows, key=lambda x: x.timestamp)
        else:
        # 否则使用默认的现金流计算逻辑
        # 如果债券在此期间到期，添加本金还款（已经添加了到期事件）
            if start_date <= self.maturity_date <= end_date:
                # 本金还款
                cashflows.append(
                    CashFlowEvent(
                        type=EventType.CASHFLOW,
                        timestamp=self.maturity_date,
                        asset_type=AssetType.BOND,
                        symbol=self.symbol,
                        amount=self.par_value,
                        cashflow_type=CashFlowType.PRINCIPAL,
                        related_position=self.symbol
                    )
                )
            
            # 如果有付息，添加利息支付
            if self.coupon_rate > 0:
                # 计算每次付息金额（年化利率除以付息频率）
                interest_per_payment = self.par_value * (self.coupon_rate / self.payment_frequency)
                
                for payment_date in self.payment_dates:
                    if start_date <= payment_date <= end_date:
                        cashflows.append(
                            CashFlowEvent(
                                type=EventType.CASHFLOW,
                                timestamp=payment_date,
                                asset_type=AssetType.BOND,
                                symbol=self.symbol,
                                amount=interest_per_payment,
                                cashflow_type=CashFlowType.INTEREST,
                                related_position=self.symbol
                            )
                        )
            
            return sorted(cashflows, key=lambda x: x.timestamp)
    
    def _count_days_excluding_leap(self, start_date: datetime, end_date: datetime) -> int:
        """计算两个日期之间的天数，闰年2月29日不计入"""
        days = 0
        current = start_date
        while current <= end_date:
            if not (current.month == 2 and current.day == 29):
                days += 1
            current += timedelta(days=1)
        return days - 1  # 减1是因为不计算结束日期

    def _is_leap_day(self, date: datetime) -> bool:
        """判断是否为闰年2月29日"""
        return date.month == 2 and date.day == 29

    def _calculate_daily_interest(self, current_date: datetime, next_payment_amount: float, days_between_payments: int) -> float:
        """计算单日利息，闰年2月29日为0"""
        if self._is_leap_day(current_date):
            return 0.0
        return next_payment_amount / days_between_payments

    def accrued_interest(self, current_date: datetime, quantity: float) -> float:
        """
        计算应计利息
        考虑付息频率的影响
        如果有明确的现金流，则根据明确的付息日期和金额计算
        闰年2月29日当日利息为0，其他日期正常计息
        """
        # 确保日期是datetime类型
        if isinstance(current_date, int):
            current_date = datetime.fromtimestamp(current_date)
        
        if self.explicit_cashflows:
            # 找到上一次付息日和付息金额
            last_payment_date = self.issue_date
            last_payment_amount = 0
            next_payment_date = None
            next_payment_amount = None
            
            for date, amount, flow_type in self.explicit_cashflows:
                if flow_type == CashFlowType.INTEREST:
                    if date <= current_date:
                        if date > last_payment_date:
                            last_payment_date = date
                            last_payment_amount = amount
                    else:
                        if next_payment_date is None or date < next_payment_date:
                            next_payment_date = date
                            next_payment_amount = amount

            if next_payment_date is None:
                return 0
            
            # 计算两个付息日之间的天数
            days_between_payments = (next_payment_date - last_payment_date).days
            
            # 计算累计利息
            accrued = 0.0
            current = last_payment_date
            while current <= current_date:
                if last_payment_date != self.issue_date:  # 如果是发行日，不计算,其余付息日都正常计算应计利息
                    last_payment_date -= timedelta(days=1)
                if current > last_payment_date:  # 不计算起始日
                    daily_interest = self._calculate_daily_interest(current, next_payment_amount, days_between_payments)
                    accrued += daily_interest
                current += timedelta(days=1)
            
            return quantity * accrued
            
        if self.coupon_rate == 0:  # 零息债券
            days_held = (current_date - self.issue_date).days
            return quantity * self.par_value * (
                (1 + self.coupon_rate) ** (days_held/365) - 1
            ) if self.coupon_rate else 0
        
        # 找到上一个付息日
        last_payment_date = self.issue_date
        for date in self.payment_dates:
            if date >= current_date:
                break
            last_payment_date = date
        
        # 计算应计利息（考虑付息频率）
        days_between_payments = 365 // self.payment_frequency
        daily_interest = self.par_value * (self.coupon_rate / self.payment_frequency) / days_between_payments
        
        # 计算每日利息并累加
        accrued = 0.0
        current = last_payment_date
        while current <= current_date:
            if current > last_payment_date:  # 不计算起始日
                if not self._is_leap_day(current):  # 只有闰年2月29日不计息
                    accrued += daily_interest
            current += timedelta(days=1)
            
        return quantity * accrued
    
    def is_matured(self, current_date: datetime) -> bool:
        """判断债券是否到期"""
        return current_date >= self.maturity_date

@dataclass
class RepoData(FixedAssetData):
    """回购类"""
    principal: float  # 回购本金
    repo_rate: float  # 回购利率
    start_date: datetime = field(default_factory=datetime.now)
    end_date: datetime = field(default_factory=lambda: datetime.now() + timedelta(days=7))
    
    def __post_init__(self):
        self.asset_type = AssetType.REPO
    
    def get_cashflows(self, start_date: datetime, end_date: datetime) -> List[CashFlowEvent]:
        """获取指定时间段内的现金流事件"""
        cashflows = []
        
        # 确保日期是datetime类型
        if isinstance(start_date, int):
            start_date = datetime.fromtimestamp(start_date)
        if isinstance(end_date, int):
            end_date = datetime.fromtimestamp(end_date)
        
        # 如果回购到期，添加到期事件、本金还款和利息
        if start_date <= self.end_date <= end_date:
            # 到期事件（不涉及实际现金流，仅用于通知）
            cashflows.append(
                CashFlowEvent(
                    type=EventType.CASHFLOW,
                    timestamp=self.end_date,
                    asset_type=AssetType.REPO,
                    symbol=self.symbol,
                    amount=0,
                    cashflow_type=CashFlowType.MATURITY,
                    related_position=self.symbol
                )
            )
            
            # 本金
            cashflows.append(
                CashFlowEvent(
                    type=EventType.CASHFLOW,
                    timestamp=self.end_date,
                    asset_type=AssetType.REPO,
                    symbol=self.symbol,
                    amount=self.principal,
                    cashflow_type=CashFlowType.PRINCIPAL,
                    related_position=self.symbol
                )
            )
            
            # 利息（只在到期时结算）
            days = (self.end_date - self.start_date).days
            interest = self.principal * self.repo_rate * days / 365
            cashflows.append(
                CashFlowEvent(
                    type=EventType.CASHFLOW,
                    timestamp=self.end_date,
                    asset_type=AssetType.REPO,
                    symbol=self.symbol,
                    amount=interest,
                    cashflow_type=CashFlowType.INTEREST,
                    related_position=self.symbol
                )
            )
        
        return cashflows
    
    def is_matured(self, current_date: datetime) -> bool:
        """判断回购是否到期"""
        return current_date >= self.end_date
    
    def accrued_interest(self, current_date: datetime, quantity: float) -> float:
        """计算应计利息"""
        # 确保日期是datetime类型
        if isinstance(current_date, int):
            current_date = datetime.fromtimestamp(current_date)
            
        if current_date < self.end_date:
            days = (current_date - self.start_date).days
            # 使用 quantity 的符号来确定应计利息的方向
            return quantity * self.repo_rate * days / 365
        return 0

@dataclass
class SubscribeRequest:
    """
    Request sending to specific gateway for subscribing tick data update.
    """

    symbol: str
    exchange: Exchange

    def __post_init__(self) -> None:
        """"""
        self.vt_symbol: str = f"{self.symbol}.{self.exchange.value}"


@dataclass
class OrderRequest:
    """
    Request sending to specific gateway for creating a new order.
    """

    symbol: str
    exchange: Exchange
    direction: Direction
    type: OrderType
    volume: float
    price: float = 0
    offset: Offset = Offset.NONE
    reference: str = ""

    def __post_init__(self) -> None:
        """"""
        self.vt_symbol: str = f"{self.symbol}.{self.exchange.value}"

    def create_order_data(self, orderid: str, gateway_name: str) -> OrderData:
        """
        Create order data from request.
        """
        order: OrderData = OrderData(
            symbol=self.symbol,
            exchange=self.exchange,
            orderid=orderid,
            type=self.type,
            direction=self.direction,
            offset=self.offset,
            price=self.price,
            volume=self.volume,
            reference=self.reference,
            gateway_name=gateway_name,
        )
        return order


@dataclass
class CancelRequest:
    """
    Request sending to specific gateway for canceling an existing order.
    """

    orderid: str
    symbol: str
    exchange: Exchange

    def __post_init__(self) -> None:
        """"""
        self.vt_symbol: str = f"{self.symbol}.{self.exchange.value}"


@dataclass
class HistoryRequest:
    """
    Request sending to specific gateway for querying history data.
    """

    symbol: str
    exchange: Exchange
    start: datetime
    end: datetime = None
    interval: Interval = None

    def __post_init__(self) -> None:
        """"""
        self.vt_symbol: str = f"{self.symbol}.{self.exchange.value}"


@dataclass
class QuoteRequest:
    """
    Request sending to specific gateway for creating a new quote.
    """

    symbol: str
    exchange: Exchange
    bid_price: float
    bid_volume: int
    ask_price: float
    ask_volume: int
    bid_offset: Offset = Offset.NONE
    ask_offset: Offset = Offset.NONE
    reference: str = ""

    def __post_init__(self) -> None:
        """"""
        self.vt_symbol: str = f"{self.symbol}.{self.exchange.value}"

    def create_quote_data(self, quoteid: str, gateway_name: str) -> QuoteData:
        """
        Create quote data from request.
        """
        quote: QuoteData = QuoteData(
            symbol=self.symbol,
            exchange=self.exchange,
            quoteid=quoteid,
            bid_price=self.bid_price,
            bid_volume=self.bid_volume,
            ask_price=self.ask_price,
            ask_volume=self.ask_volume,
            bid_offset=self.bid_offset,
            ask_offset=self.ask_offset,
            reference=self.reference,
            gateway_name=gateway_name,
        )
        return quote
